'use strict'

// Last time updated: 2019-02-20 3:31:29 PM UTC

// _______________
// getStats v1.2.0

// Open-Sourced: https://github.com/muaz-khan/getStats

// --------------------------------------------------
// Muaz Khan     - www.MuazKhan.com
// MIT License   - www.WebRTC-Experiment.com/licence
// --------------------------------------------------

var getStats = function(mediaStreamTrack, callback, interval) {
  var browserFakeUserAgent = 'Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45';

  (function(that) {
    if (!that) {
      return
    }

    if (typeof window !== 'undefined') {
      return
    }

    if (typeof global === 'undefined') {
      return
    }

    global.navigator = {
      userAgent: browserFakeUserAgent,
      getUserMedia: function() {}
    }

    if (!global.console) {
      global.console = {}
    }

    if (typeof global.console.log === 'undefined' || typeof global.console.error === 'undefined') {
      global.console.error = global.console.log = global.console.log || function() {
        console.log(arguments)
      }
    }

    if (typeof document === 'undefined') {
      that.document = {
        documentElement: {
          appendChild: function() {
            return ''
          }
        }
      }

      document.createElement = document.captureStream = document.mozCaptureStream = function() {
        var obj = {
          getContext: function() {
            return obj
          },
          play: function() {},
          pause: function() {},
          drawImage: function() {},
          toDataURL: function() {
            return ''
          }
        }
        return obj
      }

      that.HTMLVideoElement = function() {}
    }

    if (typeof location === 'undefined') {
      that.location = {
        protocol: 'file:',
        href: '',
        hash: ''
      }
    }

    if (typeof screen === 'undefined') {
      that.screen = {
        width: 0,
        height: 0
      }
    }

    if (typeof URL === 'undefined') {
      that.URL = {
        createObjectURL: function() {
          return ''
        },
        revokeObjectURL: function() {
          return ''
        }
      }
    }

    if (typeof MediaStreamTrack === 'undefined') {
      that.MediaStreamTrack = function() {}
    }

    if (typeof RTCPeerConnection === 'undefined') {
      that.RTCPeerConnection = function() {}
    }

    that.window = global
  })(typeof global !== 'undefined' ? global : null)

  var RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection

  var systemNetworkType = ((navigator.connection || {}).type || 'unknown').toString().toLowerCase()

  var getStatsResult = {
    encryption: 'sha-256',
    audio: {
      send: {
        tracks: [],
        codecs: [],
        availableBandwidth: 0,
        streams: 0,
        framerateMean: 0,
        bitrateMean: 0
      },
      recv: {
        tracks: [],
        codecs: [],
        availableBandwidth: 0,
        streams: 0,
        framerateMean: 0,
        bitrateMean: 0
      },
      bytesSent: 0,
      bytesReceived: 0,
      latency: 0,
      packetsLost: 0
    },
    video: {
      send: {
        tracks: [],
        codecs: [],
        availableBandwidth: 0,
        streams: 0,
        framerateMean: 0,
        bitrateMean: 0
      },
      recv: {
        tracks: [],
        codecs: [],
        availableBandwidth: 0,
        streams: 0,
        framerateMean: 0,
        bitrateMean: 0
      },
      bytesSent: 0,
      bytesReceived: 0,
      latency: 0,
      packetsLost: 0
    },
    bandwidth: {
      systemBandwidth: 0,
      sentPerSecond: 0,
      encodedPerSecond: 0,
      helper: {
        audioBytesSent: 0,
        videoBytestSent: 0
      },
      speed: 0
    },
    results: {},
    connectionType: {
      systemNetworkType: systemNetworkType,
      systemIpAddress: '192.168.1.2',
      local: {
        candidateType: [],
        transport: [],
        ipAddress: [],
        networkType: []
      },
      remote: {
        candidateType: [],
        transport: [],
        ipAddress: [],
        networkType: []
      }
    },
    resolutions: {
      send: {
        width: 0,
        height: 0
      },
      recv: {
        width: 0,
        height: 0
      }
    },
    internal: {
      audio: {
        send: {},
        recv: {}
      },
      video: {
        send: {},
        recv: {}
      },
      candidates: {}
    },
    nomore: function() {
      nomore = true
    }
  }

  var getStatsParser = {
    checkIfOfferer: function(result) {
      if (result.type === 'googLibjingleSession') {
        getStatsResult.isOfferer = result.googInitiator
      }
    }
  }

  var isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)

  var peer = this

  if (!(arguments[0] instanceof RTCPeerConnection)) {
    const newLocal = '1st argument is not instance of RTCPeerConnection.'
    throw newLocal
  }

  peer = arguments[0]

  if (arguments[1] instanceof MediaStreamTrack) {
    mediaStreamTrack = arguments[1] // redundant on non-safari
    callback = arguments[2]
    interval = arguments[3]
  }

  var nomore = false

  function getStatsLooper() {
    getStatsWrapper(function(results) {
      if (!results || !results.forEach) return

      results.forEach(function(result) {
        // console.error('result', result);
        Object.keys(getStatsParser).forEach(function(key) {
          if (typeof getStatsParser[key] === 'function') {
            try {
              getStatsParser[key](result)
            } catch (e) {
              console.error(e.message, e.stack, e)
            }
          }
        })
      })

      try {
        if (peer.iceConnectionState.search(/failed|closed|disconnected/gi) !== -1) {
          nomore = true
        }
      } catch (e) {
        nomore = true
      }

      if (nomore === true) {
        if (getStatsResult.datachannel) {
          getStatsResult.datachannel.state = 'close'
        }
        getStatsResult.ended = true
      }

      // allow users to access native results
      getStatsResult.results = results

      if (getStatsResult.audio && getStatsResult.video) {
        getStatsResult.bandwidth.speed = (getStatsResult.audio.bytesSent - getStatsResult.bandwidth.helper.audioBytesSent) + (getStatsResult.video.bytesSent - getStatsResult.bandwidth.helper.videoBytesSent)
        getStatsResult.bandwidth.helper.audioBytesSent = getStatsResult.audio.bytesSent
        getStatsResult.bandwidth.helper.videoBytesSent = getStatsResult.video.bytesSent
      }

      callback(getStatsResult)

      // second argument checks to see, if target-user is still connected.
      if (!nomore) {
        typeof interval !== undefined && interval && setTimeout(getStatsLooper, interval || 1000)
      }
    })
  }

  // a wrapper around getStats which hides the differences (where possible)
  // following code-snippet is taken from somewhere on the github
  function getStatsWrapper(cb) {
    // if !peer or peer.signalingState == 'closed' then return;

    if (typeof window.InstallTrigger !== 'undefined' || isSafari) { // maybe "isEdge?"
      peer.getStats(window.mediaStreamTrack || null).then(function(res) {
        var items = []
        res.forEach(function(r) {
          items.push(r)
        })
        cb(items)
      }).catch(cb)
    } else {
      peer.getStats(function(res) {
        var items = []
        res.result().forEach(function(res) {
          var item = {}
          res.names().forEach(function(name) {
            item[name] = res.stat(name)
          })
          item.id = res.id
          item.type = res.type
          item.timestamp = res.timestamp
          items.push(item)
        })
        cb(items)
      })
    }
  }

  getStatsParser.datachannel = function(result) {
    if (result.type !== 'datachannel') return

    getStatsResult.datachannel = {
      state: result.state // open or connecting
    }
  }

  getStatsParser.googCertificate = function(result) {
    if (result.type === 'googCertificate') {
      getStatsResult.encryption = result.googFingerprintAlgorithm
    }

    // Safari-11 or higher
    if (result.type === 'certificate') {
      // todo: is it possible to have different encryption methods for senders and receivers?
      // if yes, then we need to set:
      //    getStatsResult.encryption.local = value;
      //    getStatsResult.encryption.remote = value;
      getStatsResult.encryption = result.fingerprintAlgorithm
    }
  }

  getStatsParser.checkAudioTracks = function(result) {
    if (result.mediaType !== 'audio') return

    var sendrecvType = result.id.split('_').pop()
    if (result.isRemote === true) {
      sendrecvType = 'recv'
    }
    if (result.isRemote === false) {
      sendrecvType = 'send'
    }

    if (!sendrecvType) return

    if (getStatsResult.audio[sendrecvType].codecs.indexOf(result.googCodecName || 'opus') === -1) {
      getStatsResult.audio[sendrecvType].codecs.push(result.googCodecName || 'opus')
    }

    if (result.bytesSent) {
      var kilobytes = 0
      if (!getStatsResult.internal.audio[sendrecvType].prevBytesSent) {
        getStatsResult.internal.audio[sendrecvType].prevBytesSent = result.bytesSent
      }

      var bytes = result.bytesSent - getStatsResult.internal.audio[sendrecvType].prevBytesSent
      getStatsResult.internal.audio[sendrecvType].prevBytesSent = result.bytesSent

      kilobytes = bytes / 1024
      getStatsResult.audio[sendrecvType].availableBandwidth = kilobytes.toFixed(1)
      getStatsResult.audio.bytesSent = kilobytes.toFixed(1)
    }

    if (result.bytesReceived) {
      kilobytes = 0
      if (!getStatsResult.internal.audio[sendrecvType].prevBytesReceived) {
        getStatsResult.internal.audio[sendrecvType].prevBytesReceived = result.bytesReceived
      }

      bytes = result.bytesReceived - getStatsResult.internal.audio[sendrecvType].prevBytesReceived
      getStatsResult.internal.audio[sendrecvType].prevBytesReceived = result.bytesReceived

      kilobytes = bytes / 1024

      // getStatsResult.audio[sendrecvType].availableBandwidth = kilobytes.toFixed(1);
      getStatsResult.audio.bytesReceived = kilobytes.toFixed(1)
    }

    if (result.googTrackId && getStatsResult.audio[sendrecvType].tracks.indexOf(result.googTrackId) === -1) {
      getStatsResult.audio[sendrecvType].tracks.push(result.googTrackId)
    }

    // calculate latency
    if (result.googCurrentDelayMs) {
      kilobytes = 0
      if (!getStatsResult.internal.audio.prevGoogCurrentDelayMs) {
        getStatsResult.internal.audio.prevGoogCurrentDelayMs = result.googCurrentDelayMs
      }

      bytes = result.googCurrentDelayMs - getStatsResult.internal.audio.prevGoogCurrentDelayMs
      getStatsResult.internal.audio.prevGoogCurrentDelayMs = result.googCurrentDelayMs

      getStatsResult.audio.latency = bytes.toFixed(1)

      if (getStatsResult.audio.latency < 0) {
        getStatsResult.audio.latency = 0
      }
    }

    // calculate packetsLost
    if (result.packetsLost) {
      kilobytes = 0
      if (!getStatsResult.internal.audio.prevPacketsLost) {
        getStatsResult.internal.audio.prevPacketsLost = result.packetsLost
      }

      bytes = result.packetsLost - getStatsResult.internal.audio.prevPacketsLost
      getStatsResult.internal.audio.prevPacketsLost = result.packetsLost

      getStatsResult.audio.packetsLost = bytes.toFixed(1)

      if (getStatsResult.audio.packetsLost < 0) {
        getStatsResult.audio.packetsLost = 0
      }
    }
  }

  getStatsParser.checkVideoTracks = function(result) {
    if (result.mediaType !== 'video') return

    var sendrecvType = result.id.split('_').pop()
    if (result.isRemote === true) {
      sendrecvType = 'recv'
    }
    if (result.isRemote === false) {
      sendrecvType = 'send'
    }

    if (!sendrecvType) return

    if (getStatsResult.video[sendrecvType].codecs.indexOf(result.googCodecName || 'VP8') === -1) {
      getStatsResult.video[sendrecvType].codecs.push(result.googCodecName || 'VP8')
    }

    if (result.bytesSent) {
      var kilobytes = 0
      if (!getStatsResult.internal.video[sendrecvType].prevBytesSent) {
        getStatsResult.internal.video[sendrecvType].prevBytesSent = result.bytesSent
      }

      var bytes = result.bytesSent - getStatsResult.internal.video[sendrecvType].prevBytesSent
      getStatsResult.internal.video[sendrecvType].prevBytesSent = result.bytesSent

      kilobytes = bytes / 1024

      getStatsResult.video[sendrecvType].availableBandwidth = kilobytes.toFixed(1)
      getStatsResult.video.bytesSent = kilobytes.toFixed(1)
    }

    if (result.bytesReceived) {
      kilobytes = 0
      if (!getStatsResult.internal.video[sendrecvType].prevBytesReceived) {
        getStatsResult.internal.video[sendrecvType].prevBytesReceived = result.bytesReceived
      }

      bytes = result.bytesReceived - getStatsResult.internal.video[sendrecvType].prevBytesReceived
      getStatsResult.internal.video[sendrecvType].prevBytesReceived = result.bytesReceived

      kilobytes = bytes / 1024
      // getStatsResult.video[sendrecvType].availableBandwidth = kilobytes.toFixed(1);
      getStatsResult.video.bytesReceived = kilobytes.toFixed(1)
    }

    if (result.googFrameHeightReceived && result.googFrameWidthReceived) {
      getStatsResult.resolutions[sendrecvType].width = result.googFrameWidthReceived
      getStatsResult.resolutions[sendrecvType].height = result.googFrameHeightReceived
    }

    if (result.googFrameHeightSent && result.googFrameWidthSent) {
      getStatsResult.resolutions[sendrecvType].width = result.googFrameWidthSent
      getStatsResult.resolutions[sendrecvType].height = result.googFrameHeightSent
    }

    if (result.googTrackId && getStatsResult.video[sendrecvType].tracks.indexOf(result.googTrackId) === -1) {
      getStatsResult.video[sendrecvType].tracks.push(result.googTrackId)
    }

    if (result.framerateMean) {
      getStatsResult.bandwidth.framerateMean = result.framerateMean
      kilobytes = 0
      if (!getStatsResult.internal.video[sendrecvType].prevFramerateMean) {
        getStatsResult.internal.video[sendrecvType].prevFramerateMean = result.bitrateMean
      }

      bytes = result.bytesSent - getStatsResult.internal.video[sendrecvType].prevFramerateMean
      getStatsResult.internal.video[sendrecvType].prevFramerateMean = result.framerateMean

      kilobytes = bytes / 1024
      getStatsResult.video[sendrecvType].framerateMean = bytes.toFixed(1)
    }

    if (result.bitrateMean) {
      getStatsResult.bandwidth.bitrateMean = result.bitrateMean
      kilobytes = 0
      if (!getStatsResult.internal.video[sendrecvType].prevBitrateMean) {
        getStatsResult.internal.video[sendrecvType].prevBitrateMean = result.bitrateMean
      }

      bytes = result.bytesSent - getStatsResult.internal.video[sendrecvType].prevBitrateMean
      getStatsResult.internal.video[sendrecvType].prevBitrateMean = result.bitrateMean

      kilobytes = bytes / 1024
      getStatsResult.video[sendrecvType].bitrateMean = bytes.toFixed(1)
    }

    // calculate latency
    if (result.googCurrentDelayMs) {
      kilobytes = 0
      if (!getStatsResult.internal.video.prevGoogCurrentDelayMs) {
        getStatsResult.internal.video.prevGoogCurrentDelayMs = result.googCurrentDelayMs
      }

      bytes = result.googCurrentDelayMs - getStatsResult.internal.video.prevGoogCurrentDelayMs
      getStatsResult.internal.video.prevGoogCurrentDelayMs = result.googCurrentDelayMs

      getStatsResult.video.latency = bytes.toFixed(1)

      if (getStatsResult.video.latency < 0) {
        getStatsResult.video.latency = 0
      }
    }

    // calculate packetsLost
    if (result.packetsLost) {
      kilobytes = 0
      if (!getStatsResult.internal.video.prevPacketsLost) {
        getStatsResult.internal.video.prevPacketsLost = result.packetsLost
      }

      bytes = result.packetsLost - getStatsResult.internal.video.prevPacketsLost
      getStatsResult.internal.video.prevPacketsLost = result.packetsLost

      getStatsResult.video.packetsLost = bytes.toFixed(1)

      if (getStatsResult.video.packetsLost < 0) {
        getStatsResult.video.packetsLost = 0
      }
    }
  }

  getStatsParser.bweforvideo = function(result) {
    if (result.type !== 'VideoBwe') return

    getStatsResult.bandwidth.availableSendBandwidth = result.googAvailableSendBandwidth

    getStatsResult.bandwidth.googActualEncBitrate = result.googActualEncBitrate
    getStatsResult.bandwidth.googAvailableSendBandwidth = result.googAvailableSendBandwidth
    getStatsResult.bandwidth.googAvailableReceiveBandwidth = result.googAvailableReceiveBandwidth
    getStatsResult.bandwidth.googRetransmitBitrate = result.googRetransmitBitrate
    getStatsResult.bandwidth.googTargetEncBitrate = result.googTargetEncBitrate
    getStatsResult.bandwidth.googBucketDelay = result.googBucketDelay
    getStatsResult.bandwidth.googTransmitBitrate = result.googTransmitBitrate
  }

  getStatsParser.candidatePair = function(result) {
    if (result.type !== 'googCandidatePair' && result.type !== 'candidate-pair' && result.type !== 'local-candidate' && result.type !== 'remote-candidate') return

    // result.googActiveConnection means either STUN or TURN is used.

    if (result.googActiveConnection === 'true') {
      // id === 'Conn-audio-1-0'
      // localCandidateId, remoteCandidateId

      // bytesSent, bytesReceived

      Object.keys(getStatsResult.internal.candidates).forEach(function(cid) {
        var candidate = getStatsResult.internal.candidates[cid]
        if (candidate.ipAddress.indexOf(result.googLocalAddress) !== -1) {
          getStatsResult.connectionType.local.candidateType = candidate.candidateType
          getStatsResult.connectionType.local.ipAddress = candidate.ipAddress
          getStatsResult.connectionType.local.networkType = candidate.networkType
          getStatsResult.connectionType.local.transport = candidate.transport
        }
        if (candidate.ipAddress.indexOf(result.googRemoteAddress) !== -1) {
          getStatsResult.connectionType.remote.candidateType = candidate.candidateType
          getStatsResult.connectionType.remote.ipAddress = candidate.ipAddress
          getStatsResult.connectionType.remote.networkType = candidate.networkType
          getStatsResult.connectionType.remote.transport = candidate.transport
        }
      })

      getStatsResult.connectionType.transport = result.googTransportType

      var localCandidate = getStatsResult.internal.candidates[result.localCandidateId]
      if (localCandidate) {
        if (localCandidate.ipAddress) {
          getStatsResult.connectionType.systemIpAddress = localCandidate.ipAddress
        }
      }

      var remoteCandidate = getStatsResult.internal.candidates[result.remoteCandidateId]
      if (remoteCandidate) {
        if (remoteCandidate.ipAddress) {
          getStatsResult.connectionType.systemIpAddress = remoteCandidate.ipAddress
        }
      }
    }

    if (result.type === 'candidate-pair') {
      if (result.selected === true && result.nominated === true && result.state === 'succeeded') {
        // remoteCandidateId, localCandidateId, componentId
        localCandidate = getStatsResult.internal.candidates[result.remoteCandidateId]
        remoteCandidate = getStatsResult.internal.candidates[result.remoteCandidateId]

        // Firefox used above two pairs for connection
      }
    }

    if (result.type === 'local-candidate') {
      getStatsResult.connectionType.local.candidateType = result.candidateType
      getStatsResult.connectionType.local.ipAddress = result.ipAddress
      getStatsResult.connectionType.local.networkType = result.networkType
      getStatsResult.connectionType.local.transport = result.mozLocalTransport || result.transport
    }

    if (result.type === 'remote-candidate') {
      getStatsResult.connectionType.remote.candidateType = result.candidateType
      getStatsResult.connectionType.remote.ipAddress = result.ipAddress
      getStatsResult.connectionType.remote.networkType = result.networkType
      getStatsResult.connectionType.remote.transport = result.mozRemoteTransport || result.transport
    }

    if (isSafari) {
      // result.remoteCandidateId
      // todo: below line will always force "send" on Safari; find a solution
      var sendrecvType = result.localCandidateId ? 'send' : 'recv'

      if (!sendrecvType) return

      if (result.bytesSent) {
        var kilobytes = 0
        if (!getStatsResult.internal.video[sendrecvType].prevBytesSent) {
          getStatsResult.internal.video[sendrecvType].prevBytesSent = result.bytesSent
        }

        var bytes = result.bytesSent - getStatsResult.internal.video[sendrecvType].prevBytesSent
        getStatsResult.internal.video[sendrecvType].prevBytesSent = result.bytesSent

        kilobytes = bytes / 1024

        getStatsResult.video[sendrecvType].availableBandwidth = kilobytes.toFixed(1)
        getStatsResult.video.bytesSent = kilobytes.toFixed(1)
      }

      if (result.bytesReceived) {
        kilobytes = 0
        if (!getStatsResult.internal.video[sendrecvType].prevBytesReceived) {
          getStatsResult.internal.video[sendrecvType].prevBytesReceived = result.bytesReceived
        }

        bytes = result.bytesReceived - getStatsResult.internal.video[sendrecvType].prevBytesReceived
        getStatsResult.internal.video[sendrecvType].prevBytesReceived = result.bytesReceived

        kilobytes = bytes / 1024
        // getStatsResult.video[sendrecvType].availableBandwidth = kilobytes.toFixed(1);
        getStatsResult.video.bytesReceived = kilobytes.toFixed(1)
      }

      if (result.availableOutgoingBitrate) {
        kilobytes = 0
        if (!getStatsResult.internal.video[sendrecvType].prevAvailableOutgoingBitrate) {
          getStatsResult.internal.video[sendrecvType].prevAvailableOutgoingBitrate = result.availableOutgoingBitrate
        }

        bytes = result.availableOutgoingBitrate - getStatsResult.internal.video[sendrecvType].prevAvailableOutgoingBitrate
        getStatsResult.internal.video[sendrecvType].prevAvailableOutgoingBitrate = result.availableOutgoingBitrate

        kilobytes = bytes / 1024
        // getStatsResult.video[sendrecvType].availableBandwidth = kilobytes.toFixed(1);
        getStatsResult.video.availableOutgoingBitrate = kilobytes.toFixed(1)
      }

      if (result.availableIncomingBitrate) {
        kilobytes = 0
        if (!getStatsResult.internal.video[sendrecvType].prevAvailableIncomingBitrate) {
          getStatsResult.internal.video[sendrecvType].prevAvailableIncomingBitrate = result.availableIncomingBitrate
        }

        bytes = result.availableIncomingBitrate - getStatsResult.internal.video[sendrecvType].prevAvailableIncomingBitrate
        getStatsResult.internal.video[sendrecvType].prevAvailableIncomingBitrate = result.availableIncomingBitrate

        kilobytes = bytes / 1024
        // getStatsResult.video[sendrecvType].availableBandwidth = kilobytes.toFixed(1);
        getStatsResult.video.availableIncomingBitrate = kilobytes.toFixed(1)
      }
    }
  }

  var LOCAL_candidateType = {}
  var LOCAL_transport = {}
  var LOCAL_ipAddress = {}
  var LOCAL_networkType = {}

  getStatsParser.localcandidate = function(result) {
    if (result.type !== 'localcandidate' && result.type !== 'local-candidate') return
    if (!result.id) return

    if (!LOCAL_candidateType[result.id]) {
      LOCAL_candidateType[result.id] = []
    }

    if (!LOCAL_transport[result.id]) {
      LOCAL_transport[result.id] = []
    }

    if (!LOCAL_ipAddress[result.id]) {
      LOCAL_ipAddress[result.id] = []
    }

    if (!LOCAL_networkType[result.id]) {
      LOCAL_networkType[result.id] = []
    }

    if (result.candidateType && LOCAL_candidateType[result.id].indexOf(result.candidateType) === -1) {
      LOCAL_candidateType[result.id].push(result.candidateType)
    }

    if (result.transport && LOCAL_transport[result.id].indexOf(result.transport) === -1) {
      LOCAL_transport[result.id].push(result.transport)
    }

    if (result.ipAddress && LOCAL_ipAddress[result.id].indexOf(result.ipAddress + ':' + result.portNumber) === -1) {
      LOCAL_ipAddress[result.id].push(result.ipAddress + ':' + result.portNumber)
    }

    if (result.networkType && LOCAL_networkType[result.id].indexOf(result.networkType) === -1) {
      LOCAL_networkType[result.id].push(result.networkType)
    }

    getStatsResult.internal.candidates[result.id] = {
      candidateType: LOCAL_candidateType[result.id],
      ipAddress: LOCAL_ipAddress[result.id],
      portNumber: result.portNumber,
      networkType: LOCAL_networkType[result.id],
      priority: result.priority,
      transport: LOCAL_transport[result.id],
      timestamp: result.timestamp,
      id: result.id,
      type: result.type
    }

    getStatsResult.connectionType.local.candidateType = LOCAL_candidateType[result.id]
    getStatsResult.connectionType.local.ipAddress = LOCAL_ipAddress[result.id]
    getStatsResult.connectionType.local.networkType = LOCAL_networkType[result.id]
    getStatsResult.connectionType.local.transport = LOCAL_transport[result.id]
  }

  var REMOTE_candidateType = {}
  var REMOTE_transport = {}
  var REMOTE_ipAddress = {}
  var REMOTE_networkType = {}

  getStatsParser.remotecandidate = function(result) {
    if (result.type !== 'remotecandidate' && result.type !== 'remote-candidate') return
    if (!result.id) return

    if (!REMOTE_candidateType[result.id]) {
      REMOTE_candidateType[result.id] = []
    }

    if (!REMOTE_transport[result.id]) {
      REMOTE_transport[result.id] = []
    }

    if (!REMOTE_ipAddress[result.id]) {
      REMOTE_ipAddress[result.id] = []
    }

    if (!REMOTE_networkType[result.id]) {
      REMOTE_networkType[result.id] = []
    }

    if (result.candidateType && REMOTE_candidateType[result.id].indexOf(result.candidateType) === -1) {
      REMOTE_candidateType[result.id].push(result.candidateType)
    }

    if (result.transport && REMOTE_transport[result.id].indexOf(result.transport) === -1) {
      REMOTE_transport[result.id].push(result.transport)
    }

    if (result.ipAddress && REMOTE_ipAddress[result.id].indexOf(result.ipAddress + ':' + result.portNumber) === -1) {
      REMOTE_ipAddress[result.id].push(result.ipAddress + ':' + result.portNumber)
    }

    if (result.networkType && REMOTE_networkType[result.id].indexOf(result.networkType) === -1) {
      REMOTE_networkType[result.id].push(result.networkType)
    }

    getStatsResult.internal.candidates[result.id] = {
      candidateType: REMOTE_candidateType[result.id],
      ipAddress: REMOTE_ipAddress[result.id],
      portNumber: result.portNumber,
      networkType: REMOTE_networkType[result.id],
      priority: result.priority,
      transport: REMOTE_transport[result.id],
      timestamp: result.timestamp,
      id: result.id,
      type: result.type
    }

    getStatsResult.connectionType.remote.candidateType = REMOTE_candidateType[result.id]
    getStatsResult.connectionType.remote.ipAddress = REMOTE_ipAddress[result.id]
    getStatsResult.connectionType.remote.networkType = REMOTE_networkType[result.id]
    getStatsResult.connectionType.remote.transport = REMOTE_transport[result.id]
  }

  getStatsParser.dataSentReceived = function(result) {
    if (!result.googCodecName || (result.mediaType !== 'video' && result.mediaType !== 'audio')) return

    if (result.bytesSent) {
      getStatsResult[result.mediaType].bytesSent = parseInt(result.bytesSent)
    }

    if (result.bytesReceived) {
      getStatsResult[result.mediaType].bytesReceived = parseInt(result.bytesReceived)
    }
  }

  getStatsParser.inboundrtp = function(result) {
    if (!isSafari) return
    if (result.type !== 'inbound-rtp') return

    var mediaType = result.mediaType || 'audio'
    var sendrecvType = result.isRemote ? 'recv' : 'send'

    if (!sendrecvType) return

    if (result.bytesSent) {
      var kilobytes = 0
      if (!getStatsResult.internal[mediaType][sendrecvType].prevBytesSent) {
        getStatsResult.internal[mediaType][sendrecvType].prevBytesSent = result.bytesSent
      }

      var bytes = result.bytesSent - getStatsResult.internal[mediaType][sendrecvType].prevBytesSent
      getStatsResult.internal[mediaType][sendrecvType].prevBytesSent = result.bytesSent

      kilobytes = bytes / 1024

      getStatsResult[mediaType][sendrecvType].availableBandwidth = kilobytes.toFixed(1)
      getStatsResult[mediaType].bytesSent = kilobytes.toFixed(1)
    }

    if (result.bytesReceived) {
      kilobytes = 0
      if (!getStatsResult.internal[mediaType][sendrecvType].prevBytesReceived) {
        getStatsResult.internal[mediaType][sendrecvType].prevBytesReceived = result.bytesReceived
      }

      bytes = result.bytesReceived - getStatsResult.internal[mediaType][sendrecvType].prevBytesReceived
      getStatsResult.internal[mediaType][sendrecvType].prevBytesReceived = result.bytesReceived

      kilobytes = bytes / 1024
      // getStatsResult[mediaType][sendrecvType].availableBandwidth = kilobytes.toFixed(1);
      getStatsResult[mediaType].bytesReceived = kilobytes.toFixed(1)
    }
  }

  getStatsParser.outboundrtp = function(result) {
    if (!isSafari) return
    if (result.type !== 'outbound-rtp') return

    var mediaType = result.mediaType || 'audio'
    var sendrecvType = result.isRemote ? 'recv' : 'send'

    if (!sendrecvType) return

    if (result.bytesSent) {
      var kilobytes = 0
      if (!getStatsResult.internal[mediaType][sendrecvType].prevBytesSent) {
        getStatsResult.internal[mediaType][sendrecvType].prevBytesSent = result.bytesSent
      }

      var bytes = result.bytesSent - getStatsResult.internal[mediaType][sendrecvType].prevBytesSent
      getStatsResult.internal[mediaType][sendrecvType].prevBytesSent = result.bytesSent

      kilobytes = bytes / 1024

      getStatsResult[mediaType][sendrecvType].availableBandwidth = kilobytes.toFixed(1)
      getStatsResult[mediaType].bytesSent = kilobytes.toFixed(1)
    }

    if (result.bytesReceived) {
      kilobytes = 0
      if (!getStatsResult.internal[mediaType][sendrecvType].prevBytesReceived) {
        getStatsResult.internal[mediaType][sendrecvType].prevBytesReceived = result.bytesReceived
      }

      bytes = result.bytesReceived - getStatsResult.internal[mediaType][sendrecvType].prevBytesReceived
      getStatsResult.internal[mediaType][sendrecvType].prevBytesReceived = result.bytesReceived

      kilobytes = bytes / 1024
      // getStatsResult[mediaType][sendrecvType].availableBandwidth = kilobytes.toFixed(1);
      getStatsResult[mediaType].bytesReceived = kilobytes.toFixed(1)
    }
  }

  getStatsParser.track = function(result) {
    if (!isSafari) return
    if (result.type !== 'track') return

    var sendrecvType = result.remoteSource === true ? 'send' : 'recv'

    if (result.frameWidth && result.frameHeight) {
      getStatsResult.resolutions[sendrecvType].width = result.frameWidth
      getStatsResult.resolutions[sendrecvType].height = result.frameHeight
    }

    // framesSent, framesReceived
  }

  var SSRC = {
    audio: {
      send: [],
      recv: []
    },
    video: {
      send: [],
      recv: []
    }
  }

  getStatsParser.ssrc = function(result) {
    if (!result.googCodecName || (result.mediaType !== 'video' && result.mediaType !== 'audio')) return
    if (result.type !== 'ssrc') return
    var sendrecvType = result.id.split('_').pop()

    if (SSRC[result.mediaType][sendrecvType].indexOf(result.ssrc) === -1) {
      SSRC[result.mediaType][sendrecvType].push(result.ssrc)
    }

    getStatsResult[result.mediaType][sendrecvType].streams = SSRC[result.mediaType][sendrecvType].length
  }

  getStatsLooper()
}

if (typeof module !== 'undefined' /* && !!module.exports*/) {
  module.exports = getStats
}

if (typeof window.define === 'function') {
  window.define('getStats', [], function() {
    return getStats
  })
}
